﻿namespace SpikingVisualisationRLib

open SpikingAnalyticsFrameLib
open RProvider
open RProvider.RInterop
open RProvider.``base``
open RProvider.graphics
open Deedle

[<AbstractClass; Sealed>]
type PNGVisualisation() =

    // A fast PNG drawing method (line data is accumulated and passed just once)
    static let DrawPNG minX maxX minY maxY (plotData:Frame<int, string>) =
        // plotData of the form: (["t1"; "n1"; "t2"; "n2"; "weight"; "probability";])

        let MakeDimensions data =
            let MakeSerial tripletList =
                let UnTriplet triplet =
                    seq {
                        let a, b, c = triplet
                        yield a
                        yield b
                        yield c
                    }
                tripletList
                |> Seq.map (fun triplet -> UnTriplet triplet)
                |> Seq.concat
                |> Seq.toList
            let xTriplet, yTriplet =
                data
                |> List.map (fun (x, y, _, _) -> x, y)
                |> List.unzip
            (MakeSerial xTriplet), (MakeSerial yTriplet)

        let data =
            Series.observations plotData.Rows
            |> Seq.map (fun (key, row) ->
                if row.GetAs<int>("t2") <= maxX then
                    // Create pairs of (t1,t2), (n1,n2)
                    Some((row?t1, row?t2, nan), (row?n1, row?n2, nan), row?probability, row?weight)
                else
                    None
                )
            |> Seq.choose id
            |> Seq.toList

        // extract the x and y data as lists of [x1, x2, NaN, x3, x4, NaN, ...]
        let xDim, yDim = MakeDimensions data

        // draw a plot area
        namedParams [
            "x", box [minX; maxX];
            "y", box [minY; maxY];
            "type", box "n";
            "xlab", box "Time (msec)";
            "ylab", box "Neuron Index";
        ]
        |> R.plot |> ignore

        // Draw solid lines for each connection
        namedParams [
            "x", box xDim;
            "y", box yDim;
            "type", box "o";    // lines with points
            "pch", box 21;      // filled circle for points
            "bg", box "white";  // fill color for points (defaults to transparent)
        ]
        |> R.lines |> ignore

    // A slower PNG drawing method (line data is drawn line by line)
    static let DrawWeightedPNG minX maxX (plotData:Frame<int, string>) =
        // plotData of the form: (["t1"; "n1"; "t2"; "n2"; "weight"; "probability";])

        // draw a plot area
        namedParams [
            "x", box [minX; maxX];
            "y", box [0; 800];
            "type", box "n";
            "xlab", box "Time (msec)";
            "ylab", box "Neuron Index";
        ]
        |> R.plot |> ignore

        Series.observations plotData.Rows
        |> Seq.iter (fun (key, row) ->
            if row.GetAs<int>("t2") <= maxX then
                // Create pairs of (t1,t2), (n1,n2)
                if (row?probability > 0.0) && (row?weight > 0.0) then
                    // Draw line with specified weight
                    namedParams [
                        "x", box [row?t1; row?t2];
                        "y", box [row?n1; row?n2];
                        "type", box "o";
                        "lwd", box (int (row?weight/2.0))
                    ]
                    |> R.lines |> ignore
                else
                    // Draw dotted line
                    namedParams [
                        "x", box [row?t1; row?t2];
                        "y", box [row?n1; row?n2];
                        "type", box "o";
                        "lty", box "dotted";
                    ]
                    |> R.lines |> ignore
        )

    // Overlay a set of color-coded points on an existing plot
    static let OverlayFiringEventData (plotData:Frame<int, string>) maxX =
        // plotData of the form: (["time"; "nindex"; "color";])

        let xData, yData, colorData =
            Series.observations plotData.Rows
            |> Seq.map (fun (key, row) ->
                    if row.GetAs<int>("time") <= maxX then
                        Some((row.GetAs<int> "time"), (row.GetAs<int> "nindex"), (row.GetAs<string> "color"))
                    else
                        None
                )
            |> Seq.choose id
            |> Seq.toList
            |> List.unzip3

        namedParams [
            "x", box xData;
            "y", box yData;
            "type", box "p";
            "col", box colorData;
            "pch", box 16;
        ]
        |> R.points |> ignore

    /// Display the PNG in the Frame derived from the specified descriptor file
    static member PlotPNG(descriptorFilePath, maxTime, maxNeuronIndex) =

        let minX, maxX, plotData = PNGDescriptorFrame.ReadDescriptorFile(descriptorFilePath, maxTime)
        let minY, maxY = 0, maxNeuronIndex

        DrawPNG minX maxX minY maxY plotData

    /// Display the PNG in the specified Frame
    /// Input frame format: ["t1"; "n1"; "t2"; "n2"; "weight"; "probability";]
    static member PlotPNG(plotData, minX, maxX, ?minY, ?maxY) =

        let minimumY = if minY.IsSome then minY.Value else 0
        let maximumY = if maxY.IsSome then maxY.Value else 1000
        DrawPNG minX maxX minimumY maximumY plotData

    /// Display the PNG from the specified structural descriptor file where line width is controlled by Connection weight
    // slower than PlotPNG as lines are drawn one at a time
    // maxTime specified the maximum x-value (in msecs)
    static member PlotWeightedPNG(descriptorFilePath, maxTime) =

        let minX, maxX, plotData = PNGDescriptorFrame.ReadDescriptorFile(descriptorFilePath, maxTime)

        DrawWeightedPNG minX maxX plotData

    /// Display the PNG in the specified Frame where line width is controlled by Connection weight
    // slower than PlotPNG as lines are drawn one at a time
    // maxTime specified the maximum x-value (in msecs)
    /// Input frame format: ["t1"; "n1"; "t2"; "n2"; "weight"; "probability";]
    static member PlotWeightedPNG(plotData, minX, maxX) =

        DrawWeightedPNG minX maxX plotData

    /// Overlay the specified firing events
    // Drawn as colored points with time, nindex and color specified in plotData
    /// Input frame format: ["time"; "nindex"; "color";]
    static member OverlayFiringEvents(plotData, maxX) =
        OverlayFiringEventData plotData maxX
